package com.freewater.springboot.demo.aop.pointcuts;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.util.ClassUtils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * <pre>@annotation</pre>
 * 格式说明
 * <pre>@annotation</pre>(注解类型)：匹配被调用的目标对象的方法上有指定的注解
 * 匹配规则：target.getClass().getMethod("目标方法名").getDeclaredAnnotation(@annotation(目标注解))!=null
 * 这个在针对特定注解的方法日志拦截场景下应用比较多。
 * <p>
 * 链接：<a href="https://juejin.cn/post/7077425331954515999">Spring系列25：Spring AOP 切点详解</a>
 */
@Aspect
public class PointcutAnnotation {

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface MyAnn4 {
    }

    /**
     * 父类 方法上都有@MyAnn4
     */
    static class P1 {
        @MyAnn4
        public void m1() {
            System.out.println("P1 m()");
        }

        @MyAnn4
        public void m2() {
            System.out.println("P1 m2()");
        }
    }

    /**
     * 子类
     * 注意重新重写了父类的m1方法但是没有声明注解@Ann4
     * 新增了m3方法带注解@Ann4
     */
    static class C1 extends P1 {
        @Override
        public void m1() {
            System.out.println("C1 m1()");
        }

        @MyAnn4
        public void m3() {
            System.out.println("C1 m3()");
        }
    }

    // 匹配调用的方法上必须有注解
    @Pointcut("@annotation(com.freewater.springboot.demo.aop.pointcuts.PointcutAnnotation.MyAnn4)")
    public void pc() {
    }

    @Before("pc()")
    public void before(JoinPoint joinPoint) {
        System.out.println("before: " + joinPoint);
    }

    /**
     * 简单分析下:
     * <p>
     * C1中重写了m1方法，上面有没有 @Ann4，所有方法没有被拦截
     * 其它的m2在父类上有注解@Ann4，m3在子类上也有注解@Ann4，所以拦截了。
     * 最后3行输出了目标对象的3个方法上是否有注解的情况。
     */
    public static void main(String[] args) throws NoSuchMethodException {
        C1 target = new C1();
        AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.setProxyTargetClass(true);
        // 获取C1上所有接口 spring工具类提供的方法
        Class<?>[] allInterfaces = ClassUtils.getAllInterfaces(target);
        // 设置代理接口
        proxyFactory.setInterfaces(allInterfaces);
        // 添加切面
        proxyFactory.addAspect(PointcutAnnotation.class);
        // 获取代理
        C1 proxy = proxyFactory.getProxy();
        // 调用方法
        proxy.m1();
        proxy.m2();
        proxy.m3();

        System.out.println("JDK代理? " + AopUtils.isJdkDynamicProxy(proxy));
        System.out.println("CGLIB代理? " + AopUtils.isCglibProxy(proxy));

        // 目标对象的目标方法上是否直接声明了注解MyAnn4
        System.out.println(target.getClass().getMethod("m1").getDeclaredAnnotation(MyAnn4.class) != null);
        System.out.println(target.getClass().getMethod("m2").getDeclaredAnnotation(MyAnn4.class) != null);
        System.out.println(target.getClass().getMethod("m3").getDeclaredAnnotation(MyAnn4.class) != null);
    }
}
